import { createClient, SupabaseClient } from '@supabase/supabase-js';
import winston from 'winston';
import crypto from 'crypto';
import { URL } from 'url';

export interface TrackingData {
  userId: string;
  digestId: string;
  templateType: string;
  linkUrl?: string;
  linkType?: string;
  metadata?: Record<string, any>;
}

export interface EmailTrackingMetrics {
  totalSent: number;
  totalDelivered: number;
  totalOpened: number;
  totalClicked: number;
  totalBounced: number;
  totalUnsubscribed: number;
  openRate: number;
  clickRate: number;
  bounceRate: number;
  unsubscribeRate: number;
  deliveryRate: number;
}

export interface LinkAnalytics {
  url: string;
  clicks: number;
  uniqueClicks: number;
  clickRate: number;
  topReferrers: string[];
}

export class EmailTracker {
  private supabase: SupabaseClient;
  private logger: winston.Logger;
  private baseUrl: string;
  private trackingSecret: string;

  constructor(
    supabaseUrl: string,
    supabaseKey: string,
    logger: winston.Logger,
    baseUrl: string = 'https://api.arxiv-hub.com',
    trackingSecret: string = process.env.TRACKING_SECRET || 'default-secret'
  ) {
    this.supabase = createClient(supabaseUrl, supabaseKey);
    this.logger = logger;
    this.baseUrl = baseUrl;
    this.trackingSecret = trackingSecret;
  }

  /**
   * Add tracking pixels and convert links to tracked links
   */
  async addTracking(
    html: string,
    userId: string,
    digestId: string,
    templateType: string
  ): Promise<{ html: string; trackingPixelUrl: string }> {
    try {
      // Generate tracking token
      const trackingToken = this.generateTrackingToken({
        userId,
        digestId,
        templateType
      });

      // Create tracking pixel URL
      const trackingPixelUrl = `${this.baseUrl}/api/email/track/open/${trackingToken}.png`;

      // Replace all links with tracked links
      const trackedHtml = await this.replaceLinksWithTracked(html, {
        userId,
        digestId,
        templateType
      });

      // Add tracking pixel just before closing body tag
      const htmlWithPixel = trackedHtml.replace(
        '</body>',
        `<img src="${trackingPixelUrl}" width="1" height="1" style="display:none;" alt="">\n</body>`
      );

      return {
        html: htmlWithPixel,
        trackingPixelUrl
      };
    } catch (error) {
      this.logger.error('Failed to add email tracking:', error);
      return { html, trackingPixelUrl: '' };
    }
  }

  /**
   * Replace links with tracked versions
   */
  private async replaceLinksWithTracked(
    html: string,
    trackingData: TrackingData
  ): Promise<string> {
    const linkRegex = /<a[^>]+href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi;
    const unsubscribeRegex = /\{\{unsubscribeUrl\}\}/g;
    const baseUrlRegex = /\{\{baseUrl\}\}/g;

    let trackedHtml = html;
    
    // Replace template variables first
    trackedHtml = trackedHtml.replace(unsubscribeRegex, 
      await this.createUnsubscribeUrl(trackingData.userId, trackingData.digestId)
    );
    trackedHtml = trackedHtml.replace(baseUrlRegex, this.baseUrl);

    // Track all links
    const linkMatches = [...trackedHtml.matchAll(linkRegex)];
    
    for (const match of linkMatches) {
      const originalLink = match[0];
      const url = match[1];
      const linkText = match[2];

      // Skip if it's already a tracked link or a template variable
      if (url.includes('/track/') || url.startsWith('{{') || url.startsWith('mailto:')) {
        continue;
      }

      try {
        // Determine link type
        const linkType = this.determineLinkType(url, linkText);
        
        // Create tracked URL
        const trackedUrl = await this.createTrackedLink(url, {
          ...trackingData,
          linkType
        });

        // Replace the original link
        const trackedLink = originalLink.replace(url, trackedUrl);
        trackedHtml = trackedHtml.replace(originalLink, trackedLink);

      } catch (error) {
        this.logger.error(`Failed to create tracked link for ${url}:`, error);
      }
    }

    return trackedHtml;
  }

  /**
   * Generate tracking token
   */
  private generateTrackingToken(data: TrackingData): string {
    const payload = Buffer.from(JSON.stringify({
      userId: data.userId,
      digestId: data.digestId,
      templateType: data.templateType,
      timestamp: Date.now()
    })).toString('base64');

    const signature = crypto
      .createHmac('sha256', this.trackingSecret)
      .update(payload)
      .digest('hex')
      .substring(0, 16);

    return `${payload}.${signature}`;
  }

  /**
   * Verify tracking token
   */
  private verifyTrackingToken(token: string): TrackingData | null {
    try {
      const [payload, signature] = token.split('.');
      
      // Verify signature
      const expectedSignature = crypto
        .createHmac('sha256', this.trackingSecret)
        .update(payload)
        .digest('hex')
        .substring(0, 16);

      if (signature !== expectedSignature) {
        return null;
      }

      // Decode payload
      const data = JSON.parse(Buffer.from(payload, 'base64').toString());
      
      // Check if token is not too old (30 days)
      if (Date.now() - data.timestamp > 30 * 24 * 60 * 60 * 1000) {
        return null;
      }

      return data;
    } catch (error) {
      this.logger.error('Failed to verify tracking token:', error);
      return null;
    }
  }

  /**
   * Create tracked link URL
   */
  private async createTrackedLink(
    originalUrl: string,
    trackingData: TrackingData
  ): Promise<string> {
    try {
      // Store link tracking record
      const { data: linkRecord, error } = await this.supabase
        .from('email_link_tracking')
        .insert({
          user_id: trackingData.userId,
          digest_id: trackingData.digestId,
          original_url: originalUrl,
          link_type: trackingData.linkType || 'general',
          created_at: new Date().toISOString()
        })
        .select('id')
        .single();

      if (error) {
        throw error;
      }

      // Create tracking token for this specific link
      const linkToken = this.generateTrackingToken({
        ...trackingData,
        linkUrl: originalUrl,
        metadata: { linkId: linkRecord.id }
      });

      return `${this.baseUrl}/api/email/track/click/${linkToken}`;
    } catch (error) {
      this.logger.error('Failed to create tracked link:', error);
      return originalUrl; // Fallback to original URL
    }
  }

  /**
   * Create unsubscribe URL with tracking
   */
  private async createUnsubscribeUrl(userId: string, digestId: string): Promise<string> {
    const unsubscribeToken = this.generateTrackingToken({
      userId,
      digestId,
      templateType: 'unsubscribe'
    });

    return `${this.baseUrl}/api/email/unsubscribe/${unsubscribeToken}`;
  }

  /**
   * Determine link type based on URL and context
   */
  private determineLinkType(url: string, linkText: string): string {
    try {
      const urlObj = new URL(url);
      const hostname = urlObj.hostname.toLowerCase();
      const path = urlObj.pathname.toLowerCase();

      // ArXiv links
      if (hostname.includes('arxiv.org')) {
        if (path.includes('/abs/')) return 'paper_abstract';
        if (path.includes('/pdf/')) return 'paper_pdf';
        return 'arxiv';
      }

      // Internal dashboard links
      if (hostname.includes('arxiv-hub.com') || url.startsWith('/')) {
        if (path.includes('/dashboard')) return 'dashboard';
        if (path.includes('/preferences')) return 'preferences';
        if (path.includes('/bookmark')) return 'bookmark';
        if (path.includes('/share')) return 'share';
        if (path.includes('/profile')) return 'profile';
        return 'internal';
      }

      // Social sharing
      if (hostname.includes('twitter.com') || hostname.includes('facebook.com') || 
          hostname.includes('linkedin.com')) {
        return 'social_share';
      }

      // Based on link text
      const lowerText = linkText.toLowerCase();
      if (lowerText.includes('read') || lowerText.includes('paper')) return 'paper_access';
      if (lowerText.includes('bookmark')) return 'bookmark';
      if (lowerText.includes('share')) return 'share';
      if (lowerText.includes('dashboard') || lowerText.includes('visit')) return 'dashboard';
      if (lowerText.includes('preference') || lowerText.includes('setting')) return 'preferences';

      return 'external';
    } catch (error) {
      return 'general';
    }
  }

  /**
   * Track email open event
   */
  async trackEmailOpen(token: string, userAgent?: string, ipAddress?: string): Promise<boolean> {
    try {
      const trackingData = this.verifyTrackingToken(token);
      if (!trackingData) {
        this.logger.warn(`Invalid tracking token: ${token}`);
        return false;
      }

      // Check if already tracked (prevent duplicate opens within 1 hour)
      const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
      const { data: existingOpen } = await this.supabase
        .from('email_tracking_events')
        .select('id')
        .eq('user_id', trackingData.userId)
        .eq('digest_id', trackingData.digestId)
        .eq('event_type', 'open')
        .gte('created_at', oneHourAgo.toISOString())
        .limit(1)
        .single();

      if (existingOpen) {
        return true; // Already tracked recently
      }

      // Record open event
      const { error } = await this.supabase
        .from('email_tracking_events')
        .insert({
          user_id: trackingData.userId,
          digest_id: trackingData.digestId,
          event_type: 'open',
          event_data: {
            userAgent,
            ipAddress,
            templateType: trackingData.templateType
          },
          created_at: new Date().toISOString()
        });

      if (error) {
        throw error;
      }

      // Update digest open count
      await this.supabase
        .from('email_digests')
        .update({ 
          open_count: this.supabase.sql('open_count + 1'),
          delivery_status: 'opened'
        })
        .eq('digest_id', trackingData.digestId);

      // Update delivery log
      await this.supabase
        .from('email_delivery_logs')
        .update({ delivery_status: 'opened' })
        .eq('digest_id', trackingData.digestId)
        .eq('user_id', trackingData.userId);

      this.logger.info(`Email open tracked: ${trackingData.userId} - ${trackingData.digestId}`);
      return true;

    } catch (error) {
      this.logger.error('Failed to track email open:', error);
      return false;
    }
  }

  /**
   * Track email click event
   */
  async trackEmailClick(
    token: string,
    userAgent?: string,
    ipAddress?: string,
    referrer?: string
  ): Promise<string | null> {
    try {
      const trackingData = this.verifyTrackingToken(token);
      if (!trackingData) {
        this.logger.warn(`Invalid click tracking token: ${token}`);
        return null;
      }

      // Record click event
      const { error } = await this.supabase
        .from('email_tracking_events')
        .insert({
          user_id: trackingData.userId,
          digest_id: trackingData.digestId,
          event_type: 'click',
          event_data: {
            userAgent,
            ipAddress,
            referrer,
            linkUrl: trackingData.linkUrl,
            linkType: trackingData.linkType,
            templateType: trackingData.templateType
          },
          created_at: new Date().toISOString()
        });

      if (error) {
        throw error;
      }

      // Update digest click count
      await this.supabase
        .from('email_digests')
        .update({ 
          click_count: this.supabase.sql('click_count + 1'),
          delivery_status: 'clicked'
        })
        .eq('digest_id', trackingData.digestId);

      // Update delivery log
      await this.supabase
        .from('email_delivery_logs')
        .update({ delivery_status: 'clicked' })
        .eq('digest_id', trackingData.digestId)
        .eq('user_id', trackingData.userId);

      // Update link tracking if available
      if (trackingData.metadata?.linkId) {
        await this.supabase
          .from('email_link_tracking')
          .update({ 
            click_count: this.supabase.sql('click_count + 1'),
            last_clicked: new Date().toISOString()
          })
          .eq('id', trackingData.metadata.linkId);
      }

      this.logger.info(`Email click tracked: ${trackingData.userId} - ${trackingData.linkUrl}`);
      return trackingData.linkUrl || null;

    } catch (error) {
      this.logger.error('Failed to track email click:', error);
      return null;
    }
  }

  /**
   * Track bounce event
   */
  async trackEmailBounce(
    digestId: string,
    userId: string,
    bounceType: 'hard' | 'soft',
    bounceReason: string,
    bounceData?: Record<string, any>
  ): Promise<boolean> {
    try {
      // Record bounce event
      const { error } = await this.supabase
        .from('email_tracking_events')
        .insert({
          user_id: userId,
          digest_id: digestId,
          event_type: 'bounce',
          event_data: {
            bounceType,
            bounceReason,
            ...bounceData
          },
          created_at: new Date().toISOString()
        });

      if (error) {
        throw error;
      }

      // Update digest delivery status
      await this.supabase
        .from('email_digests')
        .update({ delivery_status: 'bounced' })
        .eq('digest_id', digestId);

      // Update delivery log
      await this.supabase
        .from('email_delivery_logs')
        .update({ 
          delivery_status: 'bounced',
          bounce_reason: bounceReason
        })
        .eq('digest_id', digestId)
        .eq('user_id', userId);

      // Handle hard bounces - disable email for user
      if (bounceType === 'hard') {
        await this.supabase
          .from('notification_preferences')
          .update({ is_enabled: false })
          .eq('user_id', userId)
          .eq('notification_type', 'email_digest');

        this.logger.warn(`Hard bounce - disabled email for user: ${userId}`);
      }

      this.logger.info(`Email bounce tracked: ${userId} - ${bounceType} - ${bounceReason}`);
      return true;

    } catch (error) {
      this.logger.error('Failed to track email bounce:', error);
      return false;
    }
  }

  /**
   * Track unsubscribe event
   */
  async trackUnsubscribe(
    token: string,
    unsubscribeMethod: 'click' | 'reply' | 'manual',
    userAgent?: string,
    ipAddress?: string
  ): Promise<{ success: boolean; userId?: string }> {
    try {
      const trackingData = this.verifyTrackingToken(token);
      if (!trackingData) {
        this.logger.warn(`Invalid unsubscribe token: ${token}`);
        return { success: false };
      }

      // Record unsubscribe event
      const { error } = await this.supabase
        .from('email_tracking_events')
        .insert({
          user_id: trackingData.userId,
          digest_id: trackingData.digestId,
          event_type: 'unsubscribe',
          event_data: {
            unsubscribeMethod,
            userAgent,
            ipAddress,
            templateType: trackingData.templateType
          },
          created_at: new Date().toISOString()
        });

      if (error) {
        throw error;
      }

      // Update digest unsubscribe status
      await this.supabase
        .from('email_digests')
        .update({ 
          unsubscribe_clicked: true,
          delivery_status: 'unsubscribed'
        })
        .eq('digest_id', trackingData.digestId);

      // Disable email notifications for user
      await this.supabase
        .from('notification_preferences')
        .update({ 
          is_enabled: false,
          updated_at: new Date().toISOString()
        })
        .eq('user_id', trackingData.userId)
        .eq('notification_type', 'email_digest');

      // Update all active subscriptions
      await this.supabase
        .from('user_subscriptions')
        .update({ 
          is_active: false,
          updated_at: new Date().toISOString()
        })
        .eq('user_id', trackingData.userId);

      this.logger.info(`User unsubscribed: ${trackingData.userId} via ${unsubscribeMethod}`);
      return { success: true, userId: trackingData.userId };

    } catch (error) {
      this.logger.error('Failed to track unsubscribe:', error);
      return { success: false };
    }
  }

  /**
   * Get email tracking metrics for a specific period
   */
  async getTrackingMetrics(
    startDate: Date,
    endDate: Date,
    userId?: string,
    templateType?: string
  ): Promise<EmailTrackingMetrics> {
    try {
      let deliveryQuery = this.supabase
        .from('email_delivery_logs')
        .select('delivery_status')
        .gte('delivery_timestamp', startDate.toISOString())
        .lte('delivery_timestamp', endDate.toISOString());

      if (userId) {
        deliveryQuery = deliveryQuery.eq('user_id', userId);
      }

      const { data: deliveryData, error: deliveryError } = await deliveryQuery;

      if (deliveryError) {
        throw deliveryError;
      }

      // Count delivery statuses
      const statusCounts = deliveryData.reduce((acc: Record<string, number>, log: any) => {
        acc[log.delivery_status] = (acc[log.delivery_status] || 0) + 1;
        return acc;
      }, {});

      const totalSent = deliveryData.length;
      const totalDelivered = (statusCounts.delivered || 0) + (statusCounts.opened || 0) + (statusCounts.clicked || 0);
      const totalOpened = (statusCounts.opened || 0) + (statusCounts.clicked || 0);
      const totalClicked = statusCounts.clicked || 0;
      const totalBounced = statusCounts.bounced || 0;
      const totalUnsubscribed = statusCounts.unsubscribed || 0;

      return {
        totalSent,
        totalDelivered,
        totalOpened,
        totalClicked,
        totalBounced,
        totalUnsubscribed,
        deliveryRate: totalSent > 0 ? (totalDelivered / totalSent) * 100 : 0,
        openRate: totalDelivered > 0 ? (totalOpened / totalDelivered) * 100 : 0,
        clickRate: totalOpened > 0 ? (totalClicked / totalOpened) * 100 : 0,
        bounceRate: totalSent > 0 ? (totalBounced / totalSent) * 100 : 0,
        unsubscribeRate: totalDelivered > 0 ? (totalUnsubscribed / totalDelivered) * 100 : 0
      };
    } catch (error) {
      this.logger.error('Failed to get tracking metrics:', error);
      return {
        totalSent: 0,
        totalDelivered: 0,
        totalOpened: 0,
        totalClicked: 0,
        totalBounced: 0,
        totalUnsubscribed: 0,
        deliveryRate: 0,
        openRate: 0,
        clickRate: 0,
        bounceRate: 0,
        unsubscribeRate: 0
      };
    }
  }

  /**
   * Get link analytics for emails
   */
  async getLinkAnalytics(
    startDate: Date,
    endDate: Date,
    userId?: string,
    limit: number = 50
  ): Promise<LinkAnalytics[]> {
    try {
      let query = this.supabase
        .from('email_link_tracking')
        .select('original_url, click_count, unique_clicks')
        .gte('created_at', startDate.toISOString())
        .lte('created_at', endDate.toISOString())
        .order('click_count', { ascending: false })
        .limit(limit);

      if (userId) {
        query = query.eq('user_id', userId);
      }

      const { data, error } = await query;

      if (error) {
        throw error;
      }

      return data.map(link => ({
        url: link.original_url,
        clicks: link.click_count || 0,
        uniqueClicks: link.unique_clicks || 0,
        clickRate: link.unique_clicks > 0 ? (link.click_count / link.unique_clicks) * 100 : 0,
        topReferrers: [] // Would be populated from actual referrer data
      }));

    } catch (error) {
      this.logger.error('Failed to get link analytics:', error);
      return [];
    }
  }

  /**
   * Generate tracking report
   */
  async generateTrackingReport(
    startDate: Date,
    endDate: Date,
    templateType?: string
  ): Promise<{
    overview: EmailTrackingMetrics;
    topLinks: LinkAnalytics[];
    userEngagement: Array<{
      userId: string;
      opens: number;
      clicks: number;
      lastActivity: string;
    }>;
    templatePerformance?: Array<{
      templateType: string;
      metrics: EmailTrackingMetrics;
    }>;
  }> {
    try {
      const overview = await this.getTrackingMetrics(startDate, endDate, undefined, templateType);
      const topLinks = await this.getLinkAnalytics(startDate, endDate, undefined, 10);

      // Get user engagement data
      const { data: engagementData } = await this.supabase
        .from('email_tracking_events')
        .select('user_id, event_type, created_at')
        .gte('created_at', startDate.toISOString())
        .lte('created_at', endDate.toISOString())
        .order('created_at', { ascending: false });

      const userEngagement = this.aggregateUserEngagement(engagementData || []);

      // Get template performance if not specific template requested
      let templatePerformance;
      if (!templateType) {
        const templates = ['daily_digest', 'weekly_summary', 'paper_alert', 'welcome_series_1'];
        templatePerformance = await Promise.all(
          templates.map(async (template) => ({
            templateType: template,
            metrics: await this.getTrackingMetrics(startDate, endDate, undefined, template)
          }))
        );
      }

      return {
        overview,
        topLinks,
        userEngagement: userEngagement.slice(0, 100), // Top 100 most engaged users
        templatePerformance
      };

    } catch (error) {
      this.logger.error('Failed to generate tracking report:', error);
      throw error;
    }
  }

  /**
   * Helper method to aggregate user engagement
   */
  private aggregateUserEngagement(events: any[]): Array<{
    userId: string;
    opens: number;
    clicks: number;
    lastActivity: string;
  }> {
    const userStats = new Map<string, { opens: number; clicks: number; lastActivity: string }>();

    events.forEach(event => {
      const userId = event.user_id;
      const existing = userStats.get(userId) || { opens: 0, clicks: 0, lastActivity: event.created_at };

      if (event.event_type === 'open') {
        existing.opens++;
      } else if (event.event_type === 'click') {
        existing.clicks++;
      }

      if (event.created_at > existing.lastActivity) {
        existing.lastActivity = event.created_at;
      }

      userStats.set(userId, existing);
    });

    return Array.from(userStats.entries())
      .map(([userId, stats]) => ({
        userId,
        ...stats
      }))
      .sort((a, b) => (b.opens + b.clicks) - (a.opens + a.clicks));
  }
}